最近一直在看设计模式方面的内容,一直看同样的资料打算换下。所以就看下我们是 App
都会用到的消息推送。
下面给出整理消息推送的思维导图
消息推送
消息推送介绍
我们在手机上安装的 App
在没有运行的情况下是不能够接收到信息的,但是在使用过程中一般都会收到各种各样的消息。这是为什么呢?
消息推送:在我们
App
没有运行情况下可以进行消息的传送实现。
推送的消息实现方式:本地推送和远程推送。
推送的消息内容包含:文字、提示音和图表上显示数字。
实现方式
本地推送
iOS
信息推送本地推送的实现主要是基于:UILocalNotification
。
- 本地推送步骤
本地实现方式步骤:
(1)创建
UILocalNotification
;
(2)设置通知的时间fireDate
;
(3)设置通知内容:通知的内容、通知的声音和图表数字;
(4)配置通知传递的自定义参数userInfo
;
(5)调度通知,使用scheduleLocalNotification
实现调度通知,使用presentLocalNotificationNow
实现立即调用。
- 本地推送代码
1 | //初始化本地通知对象 |
远程推送
远程推送实现一般是指我们把服务端的数据传递给客户端,但是在 App
没有开启情况下怎么实现信息推送呢?
目前有三种方式:轮询方式、SMS(push)方式和持久链接方式。
轮询方式:
最初是一种实现实时 Web
应用的方案。client
以一定的时间间隔向服务端发出请求,以频繁请求的方式来保持客户端和服务器端的同步。
轮询(Polling
)是指不管服务器端有没有更新,client
都定时的发送请求进行查询,轮询的结果可能是服务器端有新的更新过来,也可能什么也没有,只是返回个空的信息。不管结果如何,客户端处理完后到下一个定时时间点将继续下一轮的轮询。
这种同步方案的最大问题是,当客户端以固定频率向服务器发起请求的时候,服务器端的数据可能并没有更新,这样会带来很多无谓的网络传输,所以这是一种非常低效的实时方案。
长轮序方式实现流程图如下:
【图片来至于网络】
SMS(push)
方式:
服务器向注册手机或者说注册用户绑定的手机账号发送彩信,手机收到彩信后会 client
所创建的 service
截获,并解析得到信息。并产生任务栏通知,以及适当的提示。
(1)优点:
使用短信的形式实现消息传递,实时性很高。
(2)缺点:
每次信息都是采用短信形式,代价太高。
持久链接方式:
小编模拟一下长连接的实现情况:client
向 server
发起连接,server
接受client
连接,双方建立连接。client
与 server
完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
这里说一下 TCP/IP
详解上讲到的 TCP
保活功能,保活功能目的是为服务器应用提供,server
应用希望知道 client
是否崩溃,从而可以代表 client
使用资源。
已经消失,使得
server
上保留一个半开放的连接,而server
又在等待来自client
的数据,则server
将应远等待client
的数据,保活功能就是试图在server
端检测到这种半开放的连接。
下面👇讲述一种情况:一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:
(1)客户主机依然正常运行,并从服务器可达。客户的
TCP
响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。
(2)客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP
都没有响应。服务端将不能收到对探测的响应,并在75
秒后超时。服务器总共发送10
个这样的探测 ,每个间隔75
秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
(3)客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。
(4)客户机正常运行,但是服务器不可达,这种情况与2
类似,TCP
能发现的就是没有收到探查的响应。
APNs(Apple push notification server)
版本发展
iOS 3
: 引入推送通知UIApplication
的registerForRemoteNotificationTypes
与UIApplicationDelegate
的application
(didRegisterForRemoteNotificationsWithDeviceToken:
),application
(didReceiveRemoteNotification:
)。iOS 4
: 引入本地通知scheduleLocalNotification
,presentLocalNotificationNow:
,application
(didReceive:
)。iOS 5
: 加入通知中心页面。iOS 6
: 通知中心页面与iCloud
同步。iOS 7
: 后台静默推送。application
(didReceiveRemoteNotification:fetchCompletionHandle:
)iOS 8
: 重新设计notification
权限请求,Actionable
通知registerUserNotificationSettings
,UIUserNotificationAction
与UIUserNotificationCategory
,application
(handleActionWithIdentifier:forRemoteNotification:completionHandler:
) 等。iOS 9
:Text Input action
,基于HTTP/2
的推送请求UIUserNotificationActionBehavior
,全新的Provider API
等。iOS 10 : 支持
Images
,GIFs
,Audio and Video
类型, 并且有Notification Service Extension
与Notification Content Extension
,可以实现推送数据在展示前进行下载更新、定制通知UI
, 并且统一了通知类型,具有时间间隔通知、地理位置通知和日历通知。
实现流程图和原理
下面给出 APNs
推送实现流程图:
【图片来至于网络】
Provider
:指定iOS
设备应用程序提供Push
的服务器,(iOS
设备的应用程序是客户端的话,那么Provider
可以理解为服务端 –> 消息的发起者);APNS
:Apple Push Notification Service
苹果消息推送服务器;iPhone 和 MAC
:用来接收APNS
下发下来的消息;Client App
:iOS
设备上的应用程序,用来接收iphone
传递APNS
下发的消息到制定的一个客户端app
消息的最终响应者;消息推送流程过程
(1)
iOS
设备启动后连接网络, 会与苹果服务器建立一个安全的长连接;
(2)这个是系统维护的, 这也是推送的关键;
(3)用户打开App
, 授权了推送通知的权限;
(4)授权成功后,APNs
会将deviceToken
返回给iOS
终端;
(5)终端将该deviceToken
返回给指定的App
;
(6)App
拿到deviceToken
上传给我们自己的业务服务器;
(7)业务服务器向APNs
发送推送请求, 带上deviceToken
;
(8)APNs
推送内容到指定的iOS
终端;
(9)iOS
终端将内容推送给用户;
- 上面消息推送过程中要注意两个要点:
(1)App
的推送证书
如果要接受到推送信息就要在 App ID
中打开 Push Notifications
,需要我们准备好 Provisioning Profile
和 SSL
证书,并且一定要注意 Development
和 Distribution
环境是需要分开的。最后,把 SSL
证书导入到公司自有的服务平台,就可以尝试远程消息推送了。
(2)设备标识 DeviceToken
APNs
需要知道推到哪台设备上,这就是设备标识的作用。 获取设备标识的流程如下:
- 应用打开推送开关,用户要确认
client
希望获得该应用的推送消息; - 应用获得一个
DeviceToken
; - 应用将
DeviceToken
保存起来,这里就是通过[AVInstallation saveInBackground]
将DeviceToken
保存到推送server
;
当某些特定事件发生,开发者委托 推送server
来发送推送消息,这时候 推送server
的推送服务器就会给APNs
发送一则推送消息,APNs
最后消息送到用户设备。
下面给出获取 DeviceToken
的流程图:
【图片来至于网络】
客户端获取 DeviceToken
- 注册
Push
服务
client
想要给指定设备发送消息,需要知道这个设备的 DeviceToken
,然后上传到服务器。下面就来说明如何获得DeviceToken
。
想要能够接收到苹果返回的 DeviceToken
,需要先注册 Push
功能:(iOS8
和之前注册方式不同,请注意判断)。
代码实现:
1 | if (iOS8) { |
- 接受
Token
代码实现:
1 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken |
拿到了这个DeviceToken,就可以上传到服务器了,服务器就可以用这个DeviceToken给设备发消息了。
服务器推送
Apple
对于所发的消息也有严格的要求,必须要按照苹果的格式,且内容不能超过256字节(最新的 HTTP/2 最大的推送 4096 字节),如果超过,APNs则不会推送给设备。
格式如下:
1 | { |
如果要添加自定义数据,可以在 aps
外添加:
1 | { |
为了减小消息体积,可以把
page
写成p
,home
写成1、2、3
之类的,和服务器人员统一好结构。比如”p” : “1”。
客户端接受信息
client
发送消息后,经过 APNs
的处理,就可以送达我们的 App
了。
代码实现:
1 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo |
如果应用还没启动,则可以在下面的方法中获取消息:
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
如果服务器给badge设置了值的话,应用图标会一直显示数字,可以用下面的函数清空:
1 | [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; |
APNs
缺陷
【图片来至于网络】
基于二进制的旧 APNs 协议
推送分发的服务器要打开一个同 APNs
网关服务器的连接,并保持这个连接。但在旧的协议下,APNs
服务却不保证 socket
能维持这个连接。如果通道上没有消息往来,空闲下来到话,socket
将被路由掐断。也就是说:APNs 连接说断就断,那就无能为力。
(1)在旧的协议下,如果服务器响应成功的话,你将不会收到任何回应;
(2)服务器响应失败(例如,使用了一个非法的Push token
),服务器将返回了一个错误编码,并关闭这个socket
;
(3)重新发送使用这个无效token
以后发送的所有推送,可能一直不能确定你的推送是否成功的被APNs
服务器接收。
成功了不响应,失败了才响应,这个是最大的反人类。 开发者想一个很 tricky
的办法:利用这个“漏洞”,比如在每发送 10
条后故意发送一个错误的 token
,如果APNs
有响应了,就可以确认 APNs
是处在可用状态的,进而确认这 10
条消息是发送成功的。如果没有响应就说明可能连接已经中断,那么这 10
条消息很可能是丢失的,然后做进一步的处理。
代价:将导致你们的推送系统性能低下。苹果有一个名为”
feedback
“的服务,我们可以定时调用这个服务来获取invalid tokens
的列表。这个服务你只要调用一次就可以获得所有的invalid tokens
列表。所以,如果一个应用使用了很多不同公司的推送SDK
,他们将会争夺资源去轮询查找invalid tokens
列表。invalid token
越多,你们的推送系统性能将越低。而且APNs
只要一发生错误就关闭这个连接,然后重新连接。也就是“重启”socket
连接。
【图片来至于网络】
基于 HTTP/2
新的 APNs
协议
上图中的 PN2
被放到了 feedback
列表里,等待下次你调用 feedback
服务,然后重发。快速的失败然后重启比去处理错误要快。像这样的错误处理看起来跟直觉相反 —— 当错误发生的时候通过放弃处理来获得可靠性。但是重启的确是解决暂时性错误的灵丹妙药。
上面👆的新版的 APNs
的新特性优点如下:
(1)
Request
和Response
支持JSON
网络协议;
(2)APNs
支持状态码和返回error
信息:
(3)APNs
推送成功时Response
将返回状态码200
,远程通知是否发送成功再也不用靠猜了!
(4)APNs
推送失败时,Response
将返回JSON
格式的Error
信息。
最大推送长度提升到4096
字节(4Kb
)可以通过“HTTP/2 PING ”
心跳包功能检测当前APNs
连接是否可用,并能维持当前长连接;
(5)支持为不同的推送类型定义“topic”
主题;
(6)不同推送类型,只需要一种推送证书Universal Push Notification Client SSL
证书。